package main

import (
	"bufio"
	"embed"
	"flag"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/martinlindhe/notify"
	"github.com/mitchellh/go-homedir"
	"github.com/pelletier/go-toml/v2"
)

//go:embed chillhop.txt
var embedFS embed.FS

// 配置结构体（移除所有 UI 相关字段）
type Opts struct {
	FFplayPath          string `toml:"ffplay_path"`
	CachePath           string `toml:"cache_path"`
	LogLevel            string `toml:"log_level"`
	AutoPlay            bool   `toml:"auto_play"`
	CleanCacheAfterPlay bool   `toml:"clean_cache_after_play"`
	AutoPlayNext        bool   `toml:"auto_play_next"`
	Help                bool
	Version             bool
}

// 曲目结构体
type Track struct {
	ID   string
	Name string
}

var (
	opts            Opts
	tracks          []Track    // 所有曲目列表
	currentTrack    *Track     // 当前播放曲目
	nextTrack       *Track     // 预下载的下一首曲目
	isPlaying       bool       // 是否正在播放
	downloadingNext bool       // 标记是否正在下载下一首
	mu              sync.Mutex // 并发保护锁
	random          *rand.Rand // 随机数生成器
	cacheDir        string     // 缓存目录
	logFile         *os.File   // 日志文件
	ffplayCmd       *exec.Cmd  // 播放进程

	streamBaseURL = "https://stream.chillhop.com/mp3/"
	defaultConfig = `
ffplay_path = "ffplay"
cache_path = "~/.cache/chillhop-player"
log_level = "info"
auto_play = true
clean_cache_after_play = false
auto_play_next = true
`
)

func main() {
	// 初始化随机数生成器
	random = rand.New(rand.NewSource(time.Now().UnixNano()))

	// 初始化日志
	if err := initLogging(); err != nil {
		fmt.Fprintf(os.Stderr, "日志初始化失败: %v\n", err)
		os.Exit(1)
	}
	defer logFile.Close()

	logInfo("Chillhop播放器 (CLI版 v0.6.0) 启动")

	// 核心逻辑执行
	if err := realMain(); err != nil {
		logError("程序错误: %v", err)
		fmt.Fprintf(os.Stderr, "错误: %v\n", err)
		os.Exit(1)
	}
}

func realMain() error {
	// 解析命令行参数
	parseCLI()
	if opts.Help {
		printCliHelp()
		return nil
	}
	if opts.Version {
		fmt.Println("chillhop-player 0.6.0 (CLI版)")
		return nil
	}

	// 加载配置文件
	configPath, err := getConfigPath()
	if err != nil {
		return fmt.Errorf("获取配置路径失败: %w", err)
	}
	if err := ensureConfig(configPath); err != nil {
		return fmt.Errorf("创建配置文件失败: %w", err)
	}
	if err := loadConfig(configPath); err != nil {
		return fmt.Errorf("加载配置失败: %w", err)
	}

	// 初始化缓存目录
	if err := initCacheDir(); err != nil {
		return fmt.Errorf("初始化缓存目录失败: %w", err)
	}

	// 加载曲目（从 chillhop.txt）
	if err := loadTracks(); err != nil {
		return fmt.Errorf("加载曲目失败: %w", err)
	}
	if len(tracks) == 0 {
		return fmt.Errorf("chillhop.txt 中无有效曲目（格式：ID!名称）")
	}
	logInfo("成功加载 %d 首曲目", len(tracks))

	// 检查依赖（ffplay）
	if err := checkDependencies(); err != nil {
		return fmt.Errorf("依赖检查失败: %w", err)
	}

	// 设置信号处理（捕捉 Ctrl+C 退出）
	setupSignalHandler()

	// 自动播放（启动后直接开始）
	if opts.AutoPlay {
		logInfo("启动自动播放...")
		go playNextTrack()
	}

	// 阻塞主线程（无限循环，直到收到退出信号）
	select {}
}

// 解析命令行参数
func parseCLI() {
	flag.BoolVar(&opts.Help, "help", false, "显示帮助")
	flag.BoolVar(&opts.Help, "h", false, "显示帮助(短选项)")
	flag.BoolVar(&opts.Version, "version", false, "显示版本")
	flag.BoolVar(&opts.Version, "V", false, "显示版本(短选项)")
	flag.Parse()
}

// 显示帮助信息
func printCliHelp() {
	fmt.Println(`chillhop-player 0.6.0 (CLI版)
用法: chillhop-player [选项]

功能: 纯后台随机循环播放音乐，自动预下载下一首，无缝衔接

核心特性:
  - 无UI界面，所有状态通过日志和系统通知反馈
  - 播放当前曲目时，自动下载下一首，避免卡顿
  - 随机循环播放，曲目不重复（除非仅1首）
  - 支持 Ctrl+C 优雅退出

选项:
  -h, --help      显示帮助信息
  -V, --version   显示版本信息

配置文件（~/.config/chillhop-player/config.toml）:
  ffplay_path:        ffplay 可执行文件路径（默认：ffplay）
  cache_path:         歌曲缓存目录（默认：~/.cache/chillhop-player）
  auto_play:          启动后自动播放（默认：true）
  clean_cache_after_play: 退出时清理缓存（默认：false）
  auto_play_next:     播放完成自动续播（默认：true）

使用说明:
  1. 确保系统已安装 ffmpeg（提供 ffplay 播放器）
  2. 在程序同级目录放置 chillhop.txt（格式：曲目ID!曲目名称）
  3. 启动程序后自动后台播放，按 Ctrl+C 退出
`)
}

// 获取配置文件路径
func getConfigPath() (string, error) {
	home, err := homedir.Dir()
	if err != nil {
		return "", err
	}
	return filepath.Join(home, ".config", "chillhop-player", "config.toml"), nil
}

// 确保配置文件存在（不存在则创建默认配置）
func ensureConfig(path string) error {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		// 创建配置目录
		if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
			return err
		}
		// 写入默认配置
		if err := os.WriteFile(path, []byte(defaultConfig), 0644); err != nil {
			return err
		}
		logInfo("创建默认配置文件: %s", path)
	}
	return nil
}

// 加载配置文件
func loadConfig(path string) error {
	content, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	// 解析 TOML 配置
	if err := toml.Unmarshal(content, &opts); err != nil {
		return err
	}

	// 解析缓存目录（支持 ~ 符号）
	if opts.CachePath != "" {
		expanded, err := homedir.Expand(opts.CachePath)
		if err != nil {
			logWarn("缓存路径解析失败，使用默认值: %v", err)
		} else {
			cacheDir = expanded
		}
	}

	logInfo("成功加载配置文件: %s", path)
	return nil
}

// 初始化缓存目录
func initCacheDir() error {
	defaultCache, err := homedir.Expand("~/.cache/chillhop-player")
	if err != nil {
		return err
	}
	if cacheDir == "" {
		cacheDir = defaultCache
	}

	// 创建缓存目录（不存在则创建）
	if err := os.MkdirAll(cacheDir, 0755); err != nil {
		return err
	}
	logInfo("缓存目录: %s", cacheDir)
	return nil
}

// 检查依赖（ffplay）
func checkDependencies() error {
	if _, err := exec.LookPath(opts.FFplayPath); err != nil {
		return fmt.Errorf("未找到 ffplay（需安装 ffmpeg），请检查配置中的 ffplay_path")
	}
	logInfo("依赖检查通过（ffplay 已找到）")
	return nil
}

// 初始化日志系统
func initLogging() error {
	home, err := homedir.Dir()
	if err != nil {
		return err
	}
	// 创建日志目录
	logDir := filepath.Join(home, ".config", "chillhop-player", "logs")
	if err := os.MkdirAll(logDir, 0755); err != nil {
		return err
	}

	// 创建日志文件（按日期命名）
	logPath := filepath.Join(logDir, fmt.Sprintf("chillhop-player-%s.log", time.Now().Format("20060102")))
	logFile, err = os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}
	logInfo("日志文件初始化完成: %s", logPath)
	return nil
}

// 加载曲目列表（从 chillhop.txt）
func loadTracks() error {
	file, err := embedFS.Open("chillhop.txt")
	if err != nil {
		return err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	trackCount := 0
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		// 跳过空行和格式错误的行（必须包含 ! 分隔符）
		if line == "" || !strings.Contains(line, "!") {
			continue
		}
		parts := strings.SplitN(line, "!", 2)
		if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
			logWarn("无效曲目格式（已跳过）: %s", line)
			continue
		}
		// 添加到曲目列表
		tracks = append(tracks, Track{
			ID:   parts[0],
			Name: parts[1],
		})
		trackCount++
	}

	// 检查扫描错误
	if err := scanner.Err(); err != nil {
		return err
	}
	return nil
}

// 播放下一首曲目（核心逻辑）
func playNextTrack() {
	mu.Lock()
	next := nextTrack
	mu.Unlock()

	// 如果下一首未预下载，随机选择一首
	if next == nil {
		logInfo("下一首曲目未准备，随机选择中...")
		next = getRandomTrack(nil)
	}

	// 播放曲目（阻塞直到播放完成）
	if err := playTrack(next); err != nil {
		logError("播放失败: %v", err)
		sendNotification("播放失败", err.Error())
		// 播放失败后，2秒后重新尝试
		time.Sleep(2 * time.Second)
		go playNextTrack()
	}
}

// 播放指定曲目
func playTrack(track *Track) error {
	if track == nil {
		return fmt.Errorf("无效曲目")
	}

	// 检查曲目是否已缓存，未缓存则先下载
	trackPath := getTrackPath(track)
	if !fileExists(trackPath) {
		logInfo("曲目未缓存，开始下载: %s", track.Name)
		sendNotification("开始下载", "正在下载: "+track.Name)
		if err := downloadTrack(track); err != nil {
			return fmt.Errorf("下载失败: %w", err)
		}
	}

	// 停止当前播放（如果有）
	stopPlayback()

	// 更新当前播放状态
	mu.Lock()
	currentTrack = track
	isPlaying = true
	mu.Unlock()

	// 日志和通知反馈
	logInfo("开始播放: %s", track.Name)
	sendNotification("播放开始", "正在播放: "+track.Name)

	// 启动 ffplay 播放（无界面模式）
	cmd := exec.Command(opts.FFplayPath, "-nodisp", "-autoexit", trackPath)
	mu.Lock()
	ffplayCmd = cmd
	mu.Unlock()

	// 异步预下载下一首曲目
	go downloadNextTrack(track)

	// 执行播放命令（阻塞直到播放完成）
	if err := cmd.Run(); err != nil {
		mu.Lock()
		isPlaying = false
		currentTrack = nil
		mu.Unlock()
		return fmt.Errorf("播放进程失败: %w", err)
	}

	// 播放完成后的清理
	mu.Lock()
	isPlaying = false
	currentTrack = nil
	mu.Unlock()

	logInfo("播放结束: %s", track.Name)
	sendNotification("播放结束", "曲目播放完成: "+track.Name)

	// 自动续播下一首
	if opts.AutoPlayNext && len(tracks) > 0 {
		go playNextTrack()
	}

	return nil
}

// 预下载下一首曲目
func downloadNextTrack(current *Track) {
	mu.Lock()
	// 避免重复下载
	if downloadingNext {
		mu.Unlock()
		return
	}
	downloadingNext = true
	mu.Unlock()

	// 下载完成后重置标记
	defer func() {
		mu.Lock()
		downloadingNext = false
		mu.Unlock()
	}()

	// 随机选择下一首（不重复当前曲目）
	nextTrack := getRandomTrack(current)
	if nextTrack == nil {
		logWarn("无可用曲目用于预下载")
		return
	}

	// 检查是否已缓存，已缓存则直接设置为下一首
	trackPath := getTrackPath(nextTrack)
	if fileExists(trackPath) {
		logInfo("下一首曲目已缓存: %s", nextTrack.Name)
		mu.Lock()
		nextTrack = nextTrack
		mu.Unlock()
		return
	}

	// 开始预下载
	logInfo("开始预下载下一首: %s", nextTrack.Name)
	sendNotification("预下载", "正在准备下一首: "+nextTrack.Name)
	if err := downloadTrack(nextTrack); err != nil {
		logError("预下载失败: %v", err)
		sendNotification("预下载失败", "下一首准备失败: "+err.Error())
		// 下载失败，重新尝试下载其他曲目
		go downloadNextTrack(current)
		return
	}

	// 预下载成功
	logInfo("预下载完成: %s", nextTrack.Name)
	sendNotification("预下载完成", "下一首已准备: "+nextTrack.Name)
	mu.Lock()
	nextTrack = nextTrack
	mu.Unlock()
}

// 下载指定曲目
func downloadTrack(track *Track) error {
	trackPath := getTrackPath(track)

	// 发送 HTTP 请求下载曲目
	resp, err := http.Get(fmt.Sprintf("%s%s", streamBaseURL, track.ID))
	if err != nil {
		return fmt.Errorf("网络错误: %w", err)
	}
	defer resp.Body.Close()

	// 检查响应状态码
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTP 状态码错误: %d", resp.StatusCode)
	}

	// 创建本地文件
	file, err := os.Create(trackPath)
	if err != nil {
		return fmt.Errorf("创建文件失败: %w", err)
	}
	defer file.Close()

	// 写入文件（下载）
	_, err = io.Copy(file, resp.Body)
	if err != nil {
		// 下载失败，删除不完整文件
		os.Remove(trackPath)
		return fmt.Errorf("写入文件失败: %w", err)
	}

	return nil
}

// 获取随机曲目（不重复当前曲目）
func getRandomTrack(current *Track) *Track {
	if len(tracks) == 0 {
		return nil
	}
	// 只有一首曲目时直接返回
	if len(tracks) == 1 {
		return &tracks[0]
	}

	// 随机选择，避免重复当前曲目
	for {
		idx := random.Intn(len(tracks))
		candidate := &tracks[idx]
		if current == nil || candidate.ID != current.ID {
			return candidate
		}
	}
}

// 停止当前播放
func stopPlayback() {
	mu.Lock()
	defer mu.Unlock()

	if ffplayCmd != nil && ffplayCmd.Process != nil {
		// 杀死播放进程
		ffplayCmd.Process.Kill()
		logInfo("已停止当前播放")
	}
	ffplayCmd = nil
	isPlaying = false
}

// 检查文件是否存在
func fileExists(path string) bool {
	_, err := os.Stat(path)
	return !os.IsNotExist(err)
}

// 获取曲目缓存路径
func getTrackPath(track *Track) string {
	return filepath.Join(cacheDir, fmt.Sprintf("%s.mp3", track.ID))
}

// 发送系统通知
func sendNotification(title, message string) {
	go func() {
		notify.Notify("Chillhop Player", title, message, "")
	}()
}

// 清理缓存目录
func removeCacheDir() {
	mu.Lock()
	defer mu.Unlock()

	if cacheDir == "" {
		return
	}
	if err := os.RemoveAll(cacheDir); err != nil {
		logError("清理缓存失败: %v", err)
		sendNotification("缓存清理失败", "退出时清理缓存失败")
	} else {
		logInfo("缓存清理完成: %s", cacheDir)
		sendNotification("缓存清理完成", "退出时已清理所有缓存")
	}
}

// 设置信号处理（捕捉 Ctrl+C 退出）
func setupSignalHandler() {
	sigChan := make(chan os.Signal, 1)
	// 监听退出信号
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		<-sigChan
		logInfo("\n收到退出信号，正在优雅退出...")

		// 停止当前播放
		stopPlayback()

		// 按配置清理缓存
		if opts.CleanCacheAfterPlay {
			removeCacheDir()
		} else {
			logInfo("退出时保留缓存")
			sendNotification("播放器退出", "缓存已保留")
		}

		logInfo("程序已安全退出")
		sendNotification("播放器退出", "Chillhop播放器已退出")
		os.Exit(0)
	}()
}

// 日志输出（INFO 级别）
func logInfo(format string, v ...interface{}) {
	if opts.LogLevel == "debug" || opts.LogLevel == "info" {
		msg := fmt.Sprintf("[INFO] %s: %s\n", time.Now().Format("2006-01-02 15:04:05"), fmt.Sprintf(format, v...))
		io.WriteString(logFile, msg)
		// 同时输出到控制台
		fmt.Print(msg)
	}
}

// 日志输出（WARN 级别）
func logWarn(format string, v ...interface{}) {
	msg := fmt.Sprintf("[WARN] %s: %s\n", time.Now().Format("2006-01-02 15:04:05"), fmt.Sprintf(format, v...))
	io.WriteString(logFile, msg)
	fmt.Print(msg)
}

// 日志输出（ERROR 级别）
func logError(format string, v ...interface{}) {
	msg := fmt.Sprintf("[ERROR] %s: %s\n", time.Now().Format("2006-01-02 15:04:05"), fmt.Sprintf(format, v...))
	io.WriteString(logFile, msg)
	fmt.Fprint(os.Stderr, msg)
}
